﻿using DotNetty.Transport.Channels;
using FileClient.Core.Client.Processor;
using FileCommon.Models;
using FileCommon.Utils;

namespace FileClient.Core.Client.Handlers;

public class FileTransferClientHandler : ChannelHandlerAdapter
{
    private volatile FileStream _fileStream;
    private volatile OperationType _operationType;
    private volatile IClientFileProcessor _clientFileProcessor;
    private volatile string _localFileName;
    private volatile string _serverFileName;
    private volatile string _fileId;
    private volatile ProgressStatus _progressStatus = ProgressStatus.Start;
    private double preProgressRate = 0.0;
    private readonly int _progressStep;
    private int _filePieceSize;
    internal event EventHandler<TransferEventArgs> OnTransferHandle;

    internal event EventHandler<TransferEventArgs> OnTransferCompleteHandle;

    internal event EventHandler<ExceptionTransferEventArgs> OnTransferExceptionHandle;


    public FileTransferClientHandler(string localFileName, string serverFileName, string fileId, int progressStep,
        int filePieceSize,
        OperationType operationType)
    {
        _serverFileName = serverFileName;
        _localFileName = localFileName;
        _operationType = operationType;
        _progressStep = progressStep;
        _filePieceSize = filePieceSize;
        _fileId = fileId;
        _clientFileProcessor = new ClientFileProcessor(filePieceSize, localFileName, serverFileName);
    }

    public override async void ChannelRead(IChannelHandlerContext context, object message)
    {
        try
        {
            if (message is UploadFileInfo uploadFileInfo)
            {
                if (!uploadFileInfo.Success)
                {
                    throw new Exception(uploadFileInfo.ErrorMessage);
                }

                var hashMd5 = uploadFileInfo.MD5Hash;
                switch (uploadFileInfo.OperationType)
                {
                    case OperationType.Upload:
                        if (_fileStream == null)
                        {
                            _fileStream = new(_localFileName, FileMode.Open);
                        }

                        if (string.IsNullOrEmpty(hashMd5))
                        {
                            hashMd5 = HashHelper.ComputeMD5(_fileStream);
                        }

                        await _clientFileProcessor.ClientReadFile(context, uploadFileInfo, _fileStream);
                        break;
                    case OperationType.Download:
                        if (_fileStream == null)
                        {
                            _fileStream = new(_localFileName, FileMode.OpenOrCreate);
                        }

                        await _clientFileProcessor.ClientWriteFile(context, uploadFileInfo, _fileStream);
                        break;
                    default:
                        throw new Exception("指定的操作类型不正确");
                }

                var progressRate = uploadFileInfo.Complete
                    ? 100
                    : Math.Round((double)uploadFileInfo.StartPos / uploadFileInfo.FileLength * 100, 2);
                if (progressRate - preProgressRate > _progressStep || uploadFileInfo.Complete)
                {
                    _progressStatus = uploadFileInfo.Complete ? ProgressStatus.Completed : ProgressStatus.Handing;
                    OnTransferHandle?.Invoke(this, new TransferEventArgs()
                    {
                        ProgressStatus = _progressStatus,
                        OperationType = _operationType,
                        ProgressRate = progressRate,
                        FileName = _localFileName,
                        ServerFileName = _serverFileName ?? uploadFileInfo.FileName,
                        FileLength = uploadFileInfo.FileLength,
                        MD5Hash = hashMd5
                    });
                    preProgressRate = progressRate;
                }
            }
        }
        catch (Exception e)
        {
            if (message is UploadFileInfo uploadFileInfo)
            {
                if (File.Exists(_localFileName) && uploadFileInfo.OperationType == OperationType.Download &&
                    uploadFileInfo.StartPos == 0)
                {
                    _fileStream?.Dispose();
                    File.Delete(_localFileName);
                }
            }

            OnTransferExceptionHandle?.Invoke(this, new ExceptionTransferEventArgs()
            {
                FileName = _localFileName,
                ServerFileName = _serverFileName,
                Exception = e,
                MD5Hash = (message as UploadFileInfo)?.MD5Hash
            });
            _fileStream?.Dispose();
        }
    }

    public override async void ChannelActive(IChannelHandlerContext context)
    {
        try
        {
            var hashMd5 = string.Empty;
            switch (_operationType)
            {
                case OperationType.Upload:
                    if (_fileStream == null)
                    {
                        _fileStream = new(_localFileName, FileMode.Open);
                        hashMd5 = HashHelper.ComputeMD5(_fileStream);
                    }

                    await _clientFileProcessor.ClientReadFile(context, _fileStream, hashMd5);
                    break;
                case OperationType.Download:
                    if (!Directory.Exists(Path.GetDirectoryName(_localFileName)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(_localFileName));
                    }

                    if (!File.Exists(_localFileName))
                    {
                        var uploadFileInfo = new UploadFileInfo()
                        {
                            FileName = _serverFileName,
                            StartPos = 0,
                            FileLength = 0,
                            Bytes = Array.Empty<byte>(),
                            Count = _filePieceSize,
                            Complete = false,
                            OperationType = OperationType.Download,
                            FileId = _fileId,
                        };
                        await _clientFileProcessor.ClientWriteFile(context, uploadFileInfo);
                    }
                    else
                    {
                        _fileStream = new(_localFileName, FileMode.OpenOrCreate);
                        var uploadFileInfo = new UploadFileInfo()
                        {
                            FileName = _serverFileName,
                            StartPos = _fileStream.Length,
                            FileLength = 0,
                            Bytes = Array.Empty<byte>(),
                            Count = _filePieceSize,
                            Complete = false,
                            OperationType = OperationType.Download,
                            MD5Hash = hashMd5,
                            FileId = _fileId,
                        };
                        await _clientFileProcessor.ClientWriteFile(context, uploadFileInfo, _fileStream);
                    }

                    break;
            }

            _progressStatus = ProgressStatus.Start;
            if (!string.IsNullOrEmpty(hashMd5))
            {
                OnTransferHandle?.Invoke(this, new TransferEventArgs()
                {
                    ProgressStatus = _progressStatus,
                    OperationType = _operationType,
                    ProgressRate = 0,
                    FileName = _localFileName,
                    ServerFileName = _serverFileName,
                    FileLength = _fileStream == null ? 0 : _fileStream.Length,
                    MD5Hash = hashMd5
                });
            }
        }
        catch (Exception e)
        {
            OnTransferExceptionHandle?.Invoke(this, new ExceptionTransferEventArgs()
            {
                FileName = _localFileName,
                ServerFileName = _serverFileName,
                Exception = e
            });
            _fileStream?.Dispose();
        }
    }

    public override void ChannelInactive(IChannelHandlerContext context)
    {
        OnTransferCompleteHandle?.Invoke(this, new TransferEventArgs()
        {
            ProgressStatus = _progressStatus,
            OperationType = _operationType,
            FileName = _localFileName,
            ProgressRate = preProgressRate,
            ServerFileName = _serverFileName,
            FileLength = _fileStream.Length,
        });
        _fileStream?.Dispose();
    }

    public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
    {
        _progressStatus = ProgressStatus.Error;
        OnTransferHandle?.Invoke(this, new TransferEventArgs()
        {
            ProgressStatus = _progressStatus,
            OperationType = _operationType,
            ErrorMessage = exception.Message,
            FileName = _localFileName,
            ServerFileName = _serverFileName,
            FileLength = _fileStream.Length
        });
        _fileStream?.Dispose();
        base.ExceptionCaught(context, exception);
    }
}